//=============================================================================
// Single Laser Firemine
//=============================================================================
class ClawFiremineProj extends TFProjectile placeable;

var ClawFiremineLaser Laser;
var Actor ThisWall;
var vector OppositeWallLocation, WallLocation;
var bool bSpawned, bArmed, bTriggered, bOnWall, bInitialized;
var() float DamageDirectionAspect;
var sound ExplosionSound;
var ClawFiremineLaser Laser2;
var vector OppositeWallLocation2;

// laser damage
var() float LaserDamage;                   // damage when close to the mine
var() float LaserHalfDamageDistance;       // twice this dist. means damage / 4, three times means damage / 8, etc.
var() class<DamageType> LaserDamageType;
var float LastDamageTime, LastDamageTime2; // for dual laser version
var Actor LastHitActor, LastHitActor2;     // for dual laser version

var() float LaserCharge;                   // the mine has enought charge to keep the laser up for this amount of time
var() float TriggerDelay;                  // delay before exploding after the mine was triggered

var int Team;

replication
{
     unreliable if ( Role == ROLE_Authority )
	     OppositeWallLocation,OppositeWallLocation2, WallLocation, Team;
}

simulated function ProcessTouch(Actor Other, Vector HitLocation){}

simulated function PostBeginPlay()
{
	Super.PostBeginPlay();

	bSpawned = !bDeleteMe;  // only activate the mine if it didn't spawn in solid space

	if ( Role == ROLE_Authority )
    {
		Velocity = Vector(Rotation) * Speed;
		Velocity.Z += TossZ;
		//RandSpin(25000);
	}
	if ( Instigator != None )
    {
		InstigatorController = Instigator.Controller;
		if ( Instigator.Controller != none && Instigator.Controller.PlayerReplicationInfo != None && Instigator.Controller.PlayerReplicationInfo.Team != None )
			Team = Instigator.Controller.PlayerReplicationInfo.Team.TeamIndex;
	}
}

simulated function PostNetBeginPlay()
{
	if ( Role < ROLE_Authority && Physics == PHYS_None )
		Landed(-Vector(Rotation));
}

simulated function Destroyed()
{
	if ( bSpawned )
		BlowUp(Location - vector(Rotation) * 5);
	if ( Laser != None )
		Laser.Destroy();
	Super.Destroyed();
}

function Trigger(Actor Other, Pawn EventInstigator)
{
	if ( bSpawned && !bDeleteMe ) {
		bTriggered = true;
		SetTimer(TriggerDelay, False);
	}
}

singular function TakeDamage(int Damage, Pawn InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType)
{
	Trigger(InstigatedBy, InstigatedBy);
}

auto state Flying
{
	simulated function Landed( Vector HitNormal )
	{
		local Rotator NewRot;
		local vector X, Y, Z;

		if ( Level.NetMode != NM_DedicatedServer )
		{
			PlaySound(ImpactSound, SLOT_Misc);
			// explosion effects
		}

		if ( Abs(HitNormal dot vect(0,0,1)) < 0.7 ) {
			// walls (angle between HitNormal and "up" vector is 45° to 135°)
			// mine will be aligned to the wall
			NewRot = Rotator(-HitNormal);
			NewRot.Roll += 32768;
		}
		else {  // for dual laser version on floor or ceiling
			// mine will be aligned according to its impact direction
			X = -HitNormal;
			Y = X Cross Normal(Velocity);
			Z = X Cross Y;
			NewRot = OrthoRotation(X, Y, Z);
		}
		SetRotation(NewRot);
		if ( InitTripMine() ) {
			bCollideWorld = false;
			bProjTarget = true;
			PlaySound(Sound'TFMedia.Lasermine.lasermine_place');
			SetPhysics(PHYS_None);
			GotoState('OnGround');
		}
		if ( Role < ROLE_Authority )
			GotoState('');
		else
		     bUpdateSimulatedPosition = True;
	}

	simulated function Tick(float DeltaTime)
	{
		if ( Role < ROLE_Authority && WallLocation != vect(0,0,0) ) {
			Global.Tick(DeltaTime);
			GotoState('');
		}
	}

	simulated function HitWall( Vector HitNormal, Actor Wall )
	{
		Landed(HitNormal);
		if ( Physics != PHYS_None )
			ProcessTouch(Wall, Location);
		else if ( Mover(Wall) != None ) {
			SetBase(Wall);
			if (Base == None)
				Destroy();
		}
	}

	simulated function ProcessTouch(Actor Other, Vector HitLocation)
	{
		local Vector HitNormal;

		HitNormal = Normal(Location - Other.Location);
		if ( HitNormal dot Velocity < 0 )
			return;

		Velocity *= 0.8;
		Velocity -= 1.5 * (Velocity dot HitNormal) * HitNormal;

		RandSpin(100000);
		Speed = VSize(Velocity);
	}
}

state OnGround
{
	function BeginState()
	{
		InitTripMine();
	}

	function Tick(float DeltaTime)
	{
		local vector HitLocation;
		local Actor HitActor;

		if ( !bDeleteMe && bArmed )
        {
			HitLocation = TraceLaserHitLocation(Location, -vector(Rotation), HitActor);
			if ( OppositeWallLocation != HitLocation ||
            (Level.Game.IsA('ChaosDuel') && Level.Game.IsInState('WaitingForNextRound')) )
				Destroy();

		}

	}
}

function vector TraceLaserHitLocation(vector StartLocation, vector Direction, out Actor HitActor, optional bool bSecondLaser, optional bool noTraceActor)
{
	local vector HitLocation, HitNormal;
	local float DamageScale;

  //  if ( HitActor == none )  // this is really BAD
  //     return StartLocation; // gotta return something ...

	HitActor = Trace(HitLocation, HitNormal, StartLocation + 20000 * Direction, StartLocation, !noTraceActor);
	DamageScale = CalcLaserDamageScale(VSize(StartLocation - HitLocation));

	if( pawn(HitActor)!=none && pawn(hitactor).PlayerReplicationInfo.Team.TeamIndex==Team)
    {
        if(bSecondLaser) return OppositeWallLocation2;
        return OppositeWallLocation;
    }

	if ( !bSecondLaser )
    {
		if (HitActor != None && HitActor != LastHitActor )
        {
			LastDamageTime = 0;
			LastHitActor = HitActor;
		}
		else
			DamageScale *= 0.2;
		if ( Level.TimeSeconds - LastDamageTime > 0.2 )
        {
			LastDamageTime = Level.TimeSeconds;
			if ( LaserDamageType.Default.bDelayedDamage && Instigator == None && Pawn(HitActor) != None )
				Pawn(HitActor).DelayedDamageInstigatorController = InstigatorController;
			if ( HitActor != None && !HitActor.IsA('ONSPowerCore')&& int(LaserDamage * DamageScale) > 0 )
				HitActor.TakeDamage(LaserDamage * DamageScale, Instigator, HitLocation, vect(0,0,0), LaserDamageType);
		}
	}
	else {
		if ( HitActor != None && HitActor != LastHitActor2 ) {
			LastDamageTime2 = 0;
			LastHitActor2 = HitActor;
		}
		else
			DamageScale *= 0.2;
		if ( Level.TimeSeconds - LastDamageTime2 > 0.2 ) {
			LastDamageTime2 = Level.TimeSeconds;
			if ( LaserDamageType.Default.bDelayedDamage && (Instigator == None) && (Pawn(HitActor) != None) )
				Pawn(HitActor).DelayedDamageInstigatorController = InstigatorController;
			if ( HitActor != None && !HitActor.IsA('ONSPowerCore') && int(LaserDamage * DamageScale) > 0 )
				HitActor.TakeDamage(LaserDamage * DamageScale, Instigator, HitLocation, vect(0,0,0), LaserDamageType);
		}
	}
    if ( HitActor != None && HitActor.IsA('Turret'))
      Destroy();

	return HitLocation;
}

function float CalcLaserDamageScale(float Distance)
{
	return 2 ** -(Distance / LaserHalfDamageDistance);
}

simulated function Tick(float DeltaTime)
{
	if ( Role == ROLE_Authority )
		return;

	// handle attaching to wall and creating the laser effect client-side
	if ( !bOnWall && WallLocation != vect(0,0,0) ) {
		SetPhysics(PHYS_None);
		SetLocation(WallLocation);
		bOnWall = True;
	}
	if ( OppositeWallLocation != vect(0,0,0) && WallLocation != vect(0,0,0) ) {
		SpawnEffect(OppositeWallLocation, WallLocation - vector(Rotation) * 5);
		Disable('Tick');
	}
}

function bool InitTripMine()
{
	local vector HitLocation, HitNormal;

	if ( bInitialized )
		return true;

	ThisWall = Trace(HitLocation, HitNormal, Location + 20 * vector(Rotation), Location - 20 * vector(Rotation), False);
	if (ThisWall != None &&
			(ThisWall == Level || ThisWall.IsA('Mover')
			|| ThisWall.IsA('StaticMeshActor') || ThisWall.IsA('TerrainInfo')
			|| ThisWall.IsA('BlockingVolume') && ThisWall.bProjTarget ))
    {
		SetTimer(3.5, False);
		SetCollisionSize(5.0, 5.0);
		SetLocation(HitLocation);
		WallLocation = Location;
		bInitialized = True;
		return true;
	}
	return false;
}

simulated function BlowUp(Vector HitLocation)
{
	local ClawFiremineBlast ExpChain;

	Spawn(class'RocketExplosion',,, HitLocation - 20 * vector(Rotation));
	PlaySound(ExplosionSound,, 2.5 * TransientSoundVolume);
	if ( !PhysicsVolume.bWaterVolume ) {
		ExpChain = Spawn(class'ClawFiremineBlast',,, HitLocation, rotator(-vector(Rotation)));
		if ( ExpChain != NOne )
			ExpChain.InstigatorController = InstigatorController;
	}
	HurtDirectional(Damage, DamageRadius, DamageRadius * DamageDirectionAspect,
			MyDamageType, MomentumTransfer,
			HitLocation - 10 * vector(Rotation), OppositeWallLocation - HitLocation);
}

final function HurtDirectional(float DamageAmount, float MinDist, float MaxDist, class<DamageType> DamageType, float Momentum, vector HitLocation, vector HitDirection, optional bool bBothDirections)
{
	local actor Victims;
	local float damageScale, dist, scaledDist;
	local vector dir;

	if( bHurtEntry )
		return;

	bHurtEntry = True;
	HitDirection = Normal(HitDirection);
	ForEach VisibleCollidingActors(class'Actor', Victims, MaxDist, HitLocation) {
		// don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag
		if ( Victims != self && Victims.Role == ROLE_Authority && !Victims.IsA('FluidSurfaceInfo') ) {
			dir = Victims.Location - HitLocation;
			if ( bBothDirections )
				scaledDist = Lerp(Abs(Normal(dir) dot HitDirection), MinDist, MaxDist);
			else
				scaledDist = Lerp(FMax(0, Normal(dir) dot HitDirection), MinDist, MaxDist);
			dist = FMax(1, VSize(dir));
			if ( dist > scaledDist )
				continue;

			dir = dir / dist;
			damageScale = 1 - FMax(0, (dist - Victims.CollisionRadius) / scaledDist);
			if ( DamageType.Default.bDelayedDamage && (Instigator == None) && (Pawn(Victims) != None) )
				Pawn(Victims).DelayedDamageInstigatorController = InstigatorController;
			Victims.TakeDamage
			(
				damageScale * DamageAmount,
				Instigator,
				Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir,
				(damageScale * Momentum * dir),
				DamageType
			);
		}
	}
	bHurtEntry = False;
}

function Timer()
{
	if ( bTriggered ) {
		Destroy();
		return;
	}
	else if ( !bArmed ) {
		ActivateMine();
	}
}

function ActivateMine()
{
	local Actor OppositeWall;

	if ( Mover(ThisWall) != None ) {
		SetBase(ThisWall);

	}
	else if ( ThisWall != Level && StaticMeshActor(ThisWall) == None && TerrainInfo(ThisWall) == None
			 && (BlockingVolume(ThisWall) == None || !ThisWall.bProjTarget) )
    {
		Destroy();
		return;
	}

	OppositeWallLocation = TraceLaserHitLocation(Location, -vector(Rotation), OppositeWall,,true);

	if ( OppositeWall == None )
		OppositeWallLocation = Location + 20000 * -vector(Rotation);

	SpawnEffect(OppositeWallLocation - Normal(OppositeWallLocation - Location) * 5, Location + Normal(OppositeWallLocation - Location) * 5);
	bArmed = True;
	PlaySound(Sound'TFMedia.Lasermine.lasermine_activate');
}

simulated function SpawnEffect(vector HitLocation, vector SmokeLocation)
{
	local vector DVector;
	local rotator SmokeRotation;

	if ( Level.NetMode == NM_DedicatedServer )
		return;

	DVector = HitLocation - SmokeLocation;
	SmokeRotation = rotator(DVector);
	SmokeRotation.Roll = Rand(65535);

	if ( Team == 1 )
		Laser = Spawn(class'ClawFiremineLaserBlue',,, SmokeLocation, SmokeRotation);
	else
		Laser = Spawn(class'ClawFiremineLaser',,, SmokeLocation, SmokeRotation);
	Laser.AimAt(HitLocation);
}

defaultproperties
{
     DamageDirectionAspect=3.000000
     ExplosionSound=Sound'TFMedia.Lasermine.lasermine_impact'
     LaserDamage=30.000000
     LaserHalfDamageDistance=2000.000000
     LaserDamageType=Class'taskforces.DamTypeClawFiremineBurn'
     LaserCharge=999.000000
     TriggerDelay=0.200000
     Speed=1300.000000
     TossZ=50.000000
     Damage=30.000000
     DamageRadius=200.000000
     MyDamageType=Class'taskforces.DamTypeClawFiremine'
     ImpactSound=Sound'TFMedia.Lasermine.lasermine_place'
     bNetTemporary=False
     Physics=PHYS_Falling
     LifeSpan=0.000000
     Mesh=SkeletalMesh'TripMineA.Chaos_firemine'
     DrawScale=0.700000
     SoundVolume=255
     SoundRadius=100.000000
     bProjTarget=True
}
